首先先看一個簡單的例子
function identity<T>(arg: T): Type {
  return arg;
}
const stringOutput = identity<string>("Hello World");  
const numberOutput = identity<number>(25);
這是一個泛型函式,主要由兩個部分構成:
T,可以使用任意名稱。但通常習慣使用 T 表示,代表 "Type" 。這個參數允許我們在撰寫程式時保持型別彈性。除了 T 以外,以下是一些常見的泛型參數:
U:當你需要多個泛型參數時,會使用 U 來表示第二個型別參數。K(Key):物件鍵值的泛型V(Value):表示與 K 配對的值的型別。這個泛型參數通常與 K 搭配,定義物件中鍵對應的值的型別。string 和 number,使得程式碼可以適應不同型別的需求。any 可以解決部份問題,但泛型讓我們可以編寫適用於不同類型數據的代碼,仍然保持 TypeScript 的類型安全性,避免了丟失型別檢查的風險。我們可以透過 extends 將泛型限制於某些特定型別。舉個例子,如果我們想限制 identity 函式只能接受 number 或 string:
function identity<T extends number | string>(arg: T): T {
  return arg;
}
const stringOutput = identity<string>("Hello World"); 
const numberOutput = identity<number>(25);
這樣我們就限制了 T 必須是 number 或 string 型別。
extends 除了限制型別外,也可以用來確保傳入的物件具備特定屬性,會在接下來的例子說明。
接下來,以一個列表(List)元件為例,展示如何在 React 中使用泛型來定義元件的 props 型別:
type ListProps<T> = {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
};
function List<T extends { id: string | number }>({
  items,
  renderItem,
}: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}
在這個例子中,List 元件接受一個 items 陣列,其元素型別由泛型 T 決定,並透過 renderItem 函數來渲染每個元素。使用泛型讓這個元件可以處理各種型別的資料,並確保每個資料項目都有 id 屬性。
除了元件外,泛型也常用於自訂 Hook 的型別定義。
以 useFetch 為例
import { useState, useEffect } from "react";
type FetchState<T> = {
  data: T | null;
  error: Error | null;
  loading: boolean;
};
export function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    error: null,
    loading: true,
  });
  useEffect(() => {
    const fetchData = async () => {
      setState({ data: null, error: null, loading: true });
      try {
        const response = await fetch(url);
        const data = await response.json();
        setState({ data, error: null, loading: false });
      } catch (error) {
        setState({
          data: null,
          error: error instanceof Error ? error : new Error("Unknown error"),
          loading: false,
        });
      }
    };
    fetchData();
  }, [url]);
  return state;
}
這邊使用了泛型,因為 fetch 的回傳值可能會是不同的型別。
補充: 以上程式碼都可以再更優化,這邊只是讓大家了解泛型帶來的好處。未來怎麼優化可以參考之後的文章。
希望透過以上的分享能讓大家更了解泛型。
至於許多常見的 Utility Types 等都是針對以上概念去做延伸。
參考資料:
https://www.typescriptlang.org/docs/handbook/2/generics.html